﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GenericGA.generationmaker.mutation;
using GenericGA.extrandom;
using GenericGA.generationmaker.selection;
using GenericGA.generationmaker.crossover;

namespace GenericGA.generationmaker
{
    /// <summary>
    /// A class for generating populations asexually such that the new population is composed of mutated versions of the previous population.
    /// </summary>
    /// <typeparam name="P">The phenotype type, or type of the candidate solutions.</typeparam>
    public class TraditionalEvolution<P> : GenerationMaker<P>
    {

        /// <summary>
        /// A random number generator.
        /// </summary>
        private readonly ExtRandom randGen;

        /// <summary>
        /// The size of the population in the next generation.
        /// </summary>
        public int PopulationSize {
            get
            {
                return populationSize;
            }
            set
            {
                if (value < 1)
                {
                    throw new ArgumentException();
                }
                populationSize = value;
            }
        }
        private int populationSize;

        /// <summary>
        /// The mutation strategy to use for generating the next generation.
        /// </summary>
        public Mutation Mutation { get; set; }

        /// <summary>
        /// The crossover strategy to use for generating the next generation.
        /// </summary>
        public Crossover Crossover { get; set; }

        /// <summary>
        /// The selection strategy to use for choosing individuals when generating the next generation.
        /// </summary>
        public Selection<P> Selection { get; set; }
        
        /// <summary>
        /// Create a traditional generation maker.
        /// </summary>
        /// <param name="randGen">A random number generator.</param>
        /// <param name="populationSize">The number of random individuals to generate per generation.</param>
        /// <param name="mutation">The mutation strategy to use for generating the next generation.</param>
        /// <param name="crossover">The crossover strategy to use for generating the next generation.</param>
        /// <param name="selection">The selection strategy to use for choosing individuals when generating the next generation.</param>
        public TraditionalEvolution(ExtRandom randGen, int populationSize, Mutation mutation, Crossover crossover, Selection<P> selection)
        {
            this.randGen = randGen;
            this.PopulationSize = populationSize;
            this.Mutation = mutation;
            this.Crossover = crossover;
            this.Selection = selection;
        }

        /// <summary>
        /// Create a traditional generation maker using default genetic operators.
        /// </summary>
        /// <param name="randGen">A random number generator.</param>
        /// <param name="populationSize">The number of random individuals to generate per generation.</param>
        public TraditionalEvolution(ExtRandom randGen, int populationSize)
            : this(randGen, populationSize, new SinglePointMutation(randGen, 0.001), new ProbabilisticCrossover(randGen, 0.9, new SinglePointCrossover(randGen)), new FitnessProportionateSelection<P>(randGen))
        {
        }

        /// <summary>
        /// Return the next generation.
        /// </summary>
        /// <param name="currGeneration">The current generation.</param>
        /// <param name="phenotypeBuildingFunction">A phenotype building function for the creation of individuals.</param>
        /// <param name="fitnessFunction">A fitness function for the creation of individuals.</param>
        /// <returns>The next generation.</returns>
        public override List<List<ulong>> NextGeneration(List<Individual<P>> currGeneration, GA<P>.PhenotypeBuildingFunctionType phenotypeBuildingFunction, GA<P>.FitnessFunctionType fitnessFunction)
        {
            List<List<ulong>> newPopulation = new List<List<ulong>>();

            Selection.InitSelection(currGeneration, fitnessFunction);
            for (int i = 0; i < populationSize; i++)
            {
                List<ulong> offspring1 = Selection.Select().Chromosome;
                List<ulong> offspring2 = Selection.Select().Chromosome;
                
                Crossover.Cross(offspring1, offspring2);
                Mutation.Mutate(offspring1);
                Mutation.Mutate(offspring2);

                newPopulation.Add(offspring1);
                newPopulation.Add(offspring2);
            }
            
            return newPopulation;
        }

    }
}
